// Ignore argument count for test functions.
#![allow(clippy::too_many_arguments)]

use std::collections::HashSet;

use ndarray::{array, Array2};
use rstest::rstest;

use crate::tests::fixtures::{
    rand_bool,
    rand_usize,
    random_border_tile_id,
    test_data,
    RANDOM_DIRECTION,
    RANDOM_EXCLUDED_CONNECTORS,
    RANDOM_EXPOSED_EDGES,
    RANDOM_LAST_LEAVES,
    RANDOM_PREDECESSOR,
    RANDOM_USIZES,
};
use crate::tiling::slot_grid::tests::{pad_coordinates_randomly, pad_grid_randomly};
use crate::tiling::tiles::tile_list::TileList;
use crate::tiling::tiles::{ArrayInitializable, TileConnectionArray};
use crate::tiling::{
    Connectors,
    Direction,
    GridSlot,
    MissingTilePolicy,
    SlotGrid,
    TileSelectionPolicy,
    VacancyPolicy,
};
use crate::{configuration, Error};

// noinspection DuplicatedCode
#[rstest]
// region center, one iteration, no surrounding tiles, ignore vacancies
#[case::center_one_iteration_no_surrounding_tiles_ignore_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    random_border_tile_id(),
    MissingTilePolicy::Avoid,
    VacancyPolicy::Ignore,
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(2),
                    direction: Some(Direction::East),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: Default::default(),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, three iterations, no surrounding tiles, avoid vacancies
#[case::center_three_iterations_no_surrounding_tiles_avoid_vacancies(
    pad_coordinates_randomly((3, 3)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None,None, None,None, None],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    Some(11),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((3, 3)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None,None, None,None, None],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(4),
                    direction: Some(Direction::North),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![
                        Direction::North,
                        Direction::East,
                        Direction::South,
                        Direction::West,
                    ],
                    excluded_connectors: HashSet::from([
                        Connectors { north: 1, east: 4, south: 5, west: 2 },
                        Connectors { north: 4, east: 3, south: 5, west: 3 },
                    ]),
                }),
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile N present, avoid vacancies
#[case::center_two_iterations_tile_present_tile_n_present_avoid_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    Some(4),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(8),
                    direction: Some(Direction::South),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::South, Direction::West],
                    excluded_connectors: HashSet::from([
                        Connectors { north: 10, east: 9, south: 3, west: 7 },
                    ]),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, one iteration, tile NW present, avoid vacancies
#[case::center_one_iteration_tile_present_tile_nw_present_avoid_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(9),
                    direction: None,
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::North, Direction::East, Direction::South],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[1].clone(),
                }),
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    Some(4),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(9),
                    direction: None,
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::North, Direction::South],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[1].clone(),
                }),
                Some(GridSlot {
                    tile_id: Some(8),
                    direction: Some(Direction::South),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::South],
                    excluded_connectors: HashSet::new(),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, one iteration, tile SE present, avoid vacancies
#[case::center_one_iteration_tile_present_tile_se_present_avoid_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                Some(GridSlot {
                    tile_id: Some(11),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    // Declaring the western edge as exposed even though it is not may arguably be
                    // contrived, but I want to force the function not to touch the exposed edges in
                    // case the inward connector is blank.
                    exposed_edges: vec![Direction::North, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(10),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::East],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
                None,
            ],
        ]
    ),
    Some(4),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(8),
                    direction: Some(Direction::North),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::North, Direction::West],
                    excluded_connectors: HashSet::new(),
                }),
                Some(GridSlot {
                    tile_id: Some(11),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(10),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::East],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, one iteration, tile E present, avoid vacancies
#[case::center_one_iteration_tile_present_tile_e_present_avoid_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                Some(GridSlot {
                    tile_id: Some(9),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(4),
                    direction: Some(Direction::North),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::North, Direction::South, Direction::West],
                    excluded_connectors: HashSet::new(),
                }),
                Some(GridSlot {
                    tile_id: Some(9),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::South],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile W present, avoid vacancies
#[case::center_two_iterations_tile_present_tile_w_present_avoid_vacancies(
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(0),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::South, Direction::East],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(0),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[2], RANDOM_USIZES[3])
                    ),
                    exposed_edges: vec![Direction::North, Direction::South],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
                }),
                Some(GridSlot {
                    tile_id: Some(13),
                    direction: Some(Direction::North),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::North],
                    excluded_connectors: HashSet::from([
                        Connectors { north: 1, east: 4, south: 5, west: 2 },
                    ]),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, one iteration, tile W present, avoid vacancies, two neighbor associations
#[case::center_one_iteration_tile_present_tile_w_present_avoid_vacancies_two_neighbor_associations(
    (2, 2),
    *RANDOM_PREDECESSOR,
    array![
        [None, None, None, None],
        [
            None,
            None,
            None,
            Some(GridSlot {
                tile_id: Some(10),
                direction: *RANDOM_DIRECTION,
                predecessor: Some(
                    (RANDOM_USIZES[2], RANDOM_USIZES[3])
                ),
                exposed_edges: vec![],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
            }),
        ],
        [
            None,
            Some(GridSlot {
                tile_id: Some(15),
                direction: *RANDOM_DIRECTION,
                predecessor: Some(
                    (RANDOM_USIZES[1], RANDOM_USIZES[0])
                ),
                exposed_edges: vec![Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
            }),
            Some(GridSlot::new(*RANDOM_PREDECESSOR)),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    (2, 2),
    *RANDOM_PREDECESSOR,
    array![
        [None, None, None, None],
        [
            None,
            None,
            None,
            Some(GridSlot {
                tile_id: Some(10),
                direction: *RANDOM_DIRECTION,
                predecessor: Some(
                    (RANDOM_USIZES[2], RANDOM_USIZES[3])
                ),
                exposed_edges: vec![],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
            }),
        ],
        [
            None,
            Some(GridSlot {
                tile_id: Some(15),
                direction: *RANDOM_DIRECTION,
                predecessor: Some(
                    (RANDOM_USIZES[1], RANDOM_USIZES[0])
                ),
                exposed_edges: vec![],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[2].clone(),
            }),
            Some(GridSlot {
                tile_id: Some(14),
                direction: Some(Direction::North),
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::North, Direction::East],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile N, NW present, avoid vacancies, backtrack
#[case::center_two_iterations_tile_present_tile_n_nw_present_avoid_vacancies_backtrack(
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South, Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            Some(GridSlot {
                tile_id: Some(5),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            None,
            Some(GridSlot::new(Some((1, 2)))),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    (1, 2),
    *RANDOM_PREDECESSOR,
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            Some(GridSlot {
                tile_id: Some(13),
                direction: None,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![],
                excluded_connectors: HashSet::from([
                    Connectors { north: 9, east: 6, south: 10, west: 4 },
                    Connectors { north: 1, east: 4, south: 5, west: 2 },
                ]),
            }),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile N, NW present, avoid vacancies, backtrack, alternative
#[case::center_two_iterations_tile_present_tile_n_nw_present_avoid_vacancies_backtrack_alternative(
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(2),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: RANDOM_EXPOSED_EDGES[0].clone(),
                excluded_connectors: HashSet::from([
                    Connectors { north: 5, east: 5, south: 5, west: 5 },
                ]),
            }),
            Some(GridSlot {
                tile_id: Some(5),
                direction: *RANDOM_DIRECTION,
                predecessor: Some((1, 1)),
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            None,
            Some(GridSlot::new(Some((1, 2)))),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    (1, 1),
    None,
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(4),
                direction: Some(Direction::East),
                predecessor: None,
                exposed_edges: vec![Direction::East, Direction::South],
                excluded_connectors: HashSet::from([
                    Connectors { north: 5, east: 5, south: 5, west: 5 },
                    Connectors { north: 1, east: 4, south: 5, west: 2 },
                    Connectors { north: 4, east: 3, south: 5, west: 3 },
                ]),
            }),
            None,
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile N, SE present, avoid vacancies, backtrack, alternative
#[case::center_two_iterations_tile_present_tile_n_se_present_avoid_vacancies_backtrack_alternative(
    (2, 2),
    Some((3, 3)),
    array![
        [None, None, None, None],
        [
            None,
            None,
            None,
            None,
        ],
        [
            None,
            None,
            Some(GridSlot::new(Some((3, 3)))),
            None,
        ],
        [
            None,
            None,
            None,
            Some(GridSlot {
                tile_id: Some(5),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
        ],
    ],
    Some(1),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    (3, 3),
    *RANDOM_PREDECESSOR,
    array![
        [None, None, None, None],
        [
            None,
            None,
            None,
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
        [
            None,
            None,
            None,
            Some(GridSlot {
                tile_id: Some(7),
                direction: Some(Direction::North),
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::North, Direction::West],
                excluded_connectors: HashSet::from([
                    Connectors { north: 9, east: 6, south: 10, west: 4 },
                ]),
            }),
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile N, NW present, exit on missing tile
#[case::center_two_iterations_tile_present_tile_n_nw_present_exit_on_missing_tile(
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South, Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            Some(GridSlot {
                tile_id: Some(5),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            None,
            Some(GridSlot::new(Some((1, 2)))),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    None,
    MissingTilePolicy::Exit,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Err(Error::TileNotFoundException(
        "no tiles available with Connectors { north: “teal”, east: 🃟, south: 🃟, west: 🃟 }"
            .to_string()
    )),
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South, Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            Some(GridSlot {
                tile_id: Some(5),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            None,
        ],
        [
            None,
            None,
            Some(GridSlot {
                tile_id: None,
                direction: None,
                predecessor: Some((1, 2)),
                exposed_edges: vec![],
                excluded_connectors: HashSet::from([
                    Connectors { north: 10, east: 9, south: 3, west: 7 },
                    Connectors { north: 10, east: 1, south: 12, west: 9 },
                ]),
            }),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, two iterations, tile W, NW present, exit on missing tile, alternative
#[case::center_two_iterations_tile_present_tile_w_nw_present_exit_on_missing_tile_alternative(
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South, Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            None,
            None,
        ],
        [
            None,
            Some(GridSlot {
                tile_id: Some(18),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            Some(GridSlot::new(Some((1, 2)))),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    None,
    MissingTilePolicy::Exit,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Err(Error::TileNotFoundException(
        "no tiles available with Connectors { north: 🃟, east: 🃟, south: 🃟, west: “maroon” }"
            .to_string()
    )),
    (2, 2),
    Some((1, 2)),
    array![
        [None, None, None, None],
        [
            None,
            Some(GridSlot {
                tile_id: Some(0),
                direction: *RANDOM_DIRECTION,
                predecessor: None,
                exposed_edges: vec![Direction::South, Direction::East],
                excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
            }),
            None,
            None,
        ],
        [
            None,
            Some(GridSlot {
                tile_id: Some(18),
                direction: *RANDOM_DIRECTION,
                predecessor: *RANDOM_PREDECESSOR,
                exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                excluded_connectors: HashSet::new(),
            }),
            Some(GridSlot {
                tile_id: None,
                direction: None,
                predecessor: Some((1, 2)),
                exposed_edges: vec![],
                excluded_connectors: HashSet::from([
                    Connectors { north: 6, east: 5, south: 7, west: 6 },
                    Connectors { north: 6, east: 3, south: 4, west: 6 },
                ]),
            }),
            None,
        ],
        [
            None,
            None,
            None,
            None,
        ],
    ],
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, backtracking error
#[case::center_backtracking_error(
    pad_coordinates_randomly((2, 2)),
    Some(pad_coordinates_randomly((3, 2))),
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(0),
                    direction: *RANDOM_DIRECTION,
                    predecessor: None,
                    exposed_edges: vec![Direction::South, Direction::East],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
                }),
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: HashSet::new(),
                }),
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(Some(pad_coordinates_randomly((3, 2))))),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    None,
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Err(Error::ValueError(
        format!("current slot (coordinates {:?}) does not exist", pad_coordinates_randomly((3, 2)))
    )),
    pad_coordinates_randomly((3, 2)),
    Some(pad_coordinates_randomly((3, 2))),
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                Some(GridSlot {
                    tile_id: Some(0),
                    direction: *RANDOM_DIRECTION,
                    predecessor: None,
                    exposed_edges: vec![Direction::South, Direction::East],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[3].clone(),
                }),
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: HashSet::new(),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
// region center, current_slot is None, panic
#[should_panic(expected = "the current slot should be Some(GridSlot)")]
#[case::center_current_slot_is_none_panic(
    pad_coordinates_randomly((3, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::South, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                None,
                Some(GridSlot::new(*RANDOM_PREDECESSOR)),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    Some(4),
    MissingTilePolicy::Avoid,
    if rand_bool() { VacancyPolicy::Avoid } else { VacancyPolicy::AvoidStrict },
    RANDOM_LAST_LEAVES.clone(),
    Ok(()),
    pad_coordinates_randomly((2, 2)),
    *RANDOM_PREDECESSOR,
    pad_grid_randomly(
        array![
            [None, None, None, None],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(5),
                    direction: *RANDOM_DIRECTION,
                    predecessor: Some(
                        (RANDOM_USIZES[0], RANDOM_USIZES[1])
                    ),
                    exposed_edges: vec![Direction::East, Direction::West],
                    excluded_connectors: RANDOM_EXCLUDED_CONNECTORS[0].clone(),
                }),
                None,
            ],
            [
                None,
                None,
                Some(GridSlot {
                    tile_id: Some(8),
                    direction: Some(Direction::South),
                    predecessor: *RANDOM_PREDECESSOR,
                    exposed_edges: vec![Direction::South, Direction::West],
                    excluded_connectors: HashSet::from([
                        Connectors { north: 10, east: 9, south: 3, west: 7 },
                    ]),
                }),
                None,
            ],
            [
                None,
                None,
                None,
                None,
            ],
        ]
    ),
    RANDOM_LAST_LEAVES.clone(),
)]
// endregion
fn test_fill_slot(
    #[case] current_slot: (usize, usize),
    #[case] current_predecessor: Option<(usize, usize)>,
    #[case] grid: Array2<Option<GridSlot>>,
    #[case] border_tile_id: Option<usize>,
    #[case] missing_tile_policy: MissingTilePolicy,
    #[case] vacancy_policy: VacancyPolicy,
    #[case] last_leaves: Vec<(usize, usize)>,
    #[case] expected_result: Result<(), Error>,
    #[case] expected_current_slot: (usize, usize),
    #[case] expected_current_predecessor: Option<(usize, usize)>,
    #[case] expected_grid: Array2<Option<GridSlot>>,
    #[case] expected_last_leaves: Vec<(usize, usize)>,
) {
    let tile_list = TileList::new(
        configuration::load(Some(test_data("fill_slot_config.yaml")), None)
            .expect("the configuration should be valid"),
    )
    .expect("fill_slot_config.yaml should be accessible");
    let mut slot_grid = SlotGrid {
        num_rows: grid.nrows() - 1,
        num_cols: grid.ncols() - 1,
        grid,
        border_tile_id,
        tile_connections: TileConnectionArray::new(&tile_list.tiles),
        tile_list,
        missing_tile_policy,
        tile_selection_policy: TileSelectionPolicy::Priority,
        vacancy_policy,
        current_slot,
        current_predecessor,
        last_leaves,
        exposed_edge_count: rand_usize(1, 15),
    };

    let result = slot_grid.fill_slot();

    assert_eq!(result, expected_result);
    assert_eq!(slot_grid.current_slot, expected_current_slot);
    assert_eq!(slot_grid.current_predecessor, expected_current_predecessor);
    assert_eq!(slot_grid.grid, expected_grid);
    assert_eq!(slot_grid.last_leaves, expected_last_leaves);
}
